#pragma once


#include "MessageIdentifiers.h"
#include "RakPeerInterface.h"
#include "RakNetStatistics.h"
#include "RakNetTypes.h"
#include "BitStream.h"
#include "RakSleep.h"
#include "PacketLogger.h"
#include <assert.h>
#include <cstdio>
#include <cstring>
#include <stdlib.h>
#include "Kbhit.h"
#include <stdio.h>
#include <string.h>
#include "Gets.h"

#include "GameManager.h"
#include "Msg.h"
#include "Defines.h"

class hammurabiNet
{
private:
	hammurabiNet()
	{

	}

	unsigned char GetPacketIdentifier(RakNet::Packet *p)
	{
		if (p==0)
			return 255;

		if ((unsigned char)p->data[0] == ID_TIMESTAMP)
		{
			RakAssert(p->length > sizeof(RakNet::MessageID) + sizeof(RakNet::Time));
			return (unsigned char) p->data[sizeof(RakNet::MessageID) + sizeof(RakNet::Time)];
		}
		else
			return (unsigned char) p->data[0];
	}

	// Holds packets
	RakNet::Packet* p;
	RakNet::RakNetStatistics *rss;
	// Pointers to the interfaces of our server and client.
	// Note we can easily have both in the same program
	RakNet::RakPeerInterface *server;
	//	client->InitializeSecurity(0,0,0,0);
	//RakNet::PacketLogger packetLogger;
	//client->AttachPlugin(&packetLogger);
	char message[MESSAGE_BUFF];
	// Holds user data
	char ip[64], portstring[30], clientPort[30];
	RakNet::SocketDescriptor socketDescriptors[2];
	// GetPacketIdentifier returns this
	unsigned char packetIdentifier;
	RakNet::SystemAddress clientID;
	msg gameState[MAX_CLIENTS];
	msg* games;
	gameManager gameMath;
	RakNet::SystemAddress* clients[MAX_CLIENTS];
	RakNet::SystemAddress lastClient;

	RakNet::SystemAddress * getUnusedClient(int& client)
	{
		for (int i = 0; i < MAX_CLIENTS; i++)
		{
			if(!clients[i])
			{
				clients[i] = new RakNet::SystemAddress;
				client = i;
				return clients[i];
			}
		}
		return 0;
	}

	RakNet::SystemAddress * setUnusedClient(int& client, RakNet::SystemAddress& addr)
	{
		for (int i = 0; i < MAX_CLIENTS; i++)
		{
			if(!clients[i])
			{
				clients[i] = new RakNet::SystemAddress(addr);
				client = i;
				return clients[i];
			}
		}
		return 0;
	}

	void setDefaultGameState(int number)
	{
		gameState[number].copy(gameMath.newGame());
	}

	void sendGameState(int number)
	{
		msg state = gameState[number];
		state.convertTo(message);

		server->Send(message, strlen(message)+1, HIGH_PRIORITY, RELIABLE_ORDERED, 0, *clients[number], false);
		setVoidGameState(number);
	}

	void setVoidGameState(int number)
	{
		gameState[number].year	  = CLEAN_NUMBER;
		gameState[number].starved = CLEAN_NUMBER;
		gameState[number].plague  = CLEAN_NUMBER;
		gameState[number].pop     = CLEAN_NUMBER;
		gameState[number].newbs   = CLEAN_NUMBER;
		gameState[number].acres   = CLEAN_NUMBER;
		gameState[number].farmed  = CLEAN_NUMBER;
		gameState[number].rats    = CLEAN_NUMBER;
		gameState[number].bushels = CLEAN_NUMBER;
		gameState[number].bpa     = CLEAN_NUMBER;
		gameState[number].buysell = CLEAN_NUMBER;
		gameState[number].feed    = CLEAN_NUMBER;
		gameState[number].plant   = CLEAN_NUMBER;
	}

	int getClientID(RakNet::SystemAddress& ipv4)
	{
		for (int i = 0; i < MAX_CLIENTS; i++)
		{
			if(clients[i])
			{
				if(*clients[i] == ipv4)
				{
					return i;
				}
			}
		}
		return -1;
	}

	void deleteClientInfo(int number)
	{
		if(clients[number])
		{
			delete clients[number];
			clients[number] = 0;
		}
		setVoidGameState(number);
	}
public:
	static hammurabiNet* getInstance()
	{
		static hammurabiNet* instance;
		if(!instance)
			instance = new hammurabiNet;
		return instance;
	}
	
	~hammurabiNet()
	{
		if(server){
			server->Shutdown(300);
			// We're done with the network
			RakNet::RakPeerInterface::DestroyInstance(server);
			server = 0;
		}
		for (int i = 0; i < MAX_CLIENTS; i++)
		{
			deleteClientInfo(i);
		}
	}

	int input()
	{

		// This sleep keeps RakNet responsive
		RakSleep(30);

		if (kbhit())
		{
			// Notice what is not here: something to keep our network running.  It's
			// fine to block on gets or anything we want
			// Because the network engine was painstakingly written using threads.
			Gets(message,sizeof(message));

			if (strcmp(message, "quit")==0)
			{
				puts("Quitting.");
				return 0;
			}

			if (strcmp(message, "stat")==0)
			{
				rss=server->GetStatistics(server->GetSystemAddressFromIndex(0));
				StatisticsToString(rss, message, 2);
				printf("%s", message);
				printf("Ping %i\n", server->GetAveragePing(server->GetSystemAddressFromIndex(0)));
			}

			if (strcmp(message, "ping")==0)
			{
				server->Ping(clientID);
			}

			if (strcmp(message, "pingip")==0)
			{
				printf("Enter IP: ");
				Gets(message,sizeof(message));
				printf("Enter port: ");
				Gets(portstring,sizeof(portstring));
				if (portstring[0]==0)
					strcpy_s(portstring, "1234");
				server->Ping(message, atoi(portstring), false);
			}

			if (strcmp(message, "kick")==0)
			{
				server->CloseConnection(clientID, true, 0);
			}

			if (strcmp(message, "getconnectionlist")==0)
			{
				RakNet::SystemAddress systems[MAX_CLIENTS];
				unsigned short numConnections=MAX_CLIENTS;
				server->GetConnectionList((RakNet::SystemAddress*) &systems, &numConnections);
				for (int i=0; i < numConnections; i++)
				{
					printf("%i. %s\n", i+1, systems[i].ToString(true));
				}
			}

			if (strcmp(message, "ban")==0)
			{
				printf("Enter IP to ban.  You can use * as a wildcard\n");
				Gets(message,sizeof(message));
				server->AddToBanList(message);
				printf("IP %s added to ban list.\n", message);
			}

			if (strcmp(message, "sendstruct")==0)
			{
				int id = getClientID(lastClient);
				setDefaultGameState(id);
				msg state = gameState[id];
				state.convertTo(message);

				int number = server->Send(message, strlen(message)+1, HIGH_PRIORITY, RELIABLE_ORDERED, 0, *clients[id], false);
				setVoidGameState(id);
				if(!number)
				{
					int error;
					error = number;
				}
			}
			// message is the data to send
			// strlen(message)+1 is to send the null terminator
			// HIGH_PRIORITY doesn't actually matter here because we don't use any other priority
			// RELIABLE_ORDERED means make sure the message arrives in the right order
			// We arbitrarily pick 0 for the ordering stream
			// RakNet::UNASSIGNED_SYSTEM_ADDRESS means don't exclude anyone from the broadcast
			// true means broadcast the message to everyone connected
			//server->Send(message, (const int) strlen(message)+1, HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true);
		}
		return 1;
	}

	int init()
	{
		// Pointers to the interfaces of our server and client.
		// Note we can easily have both in the same program
		server=RakNet::RakPeerInterface::GetInstance();

		//server->SetIncomingPassword("R", (int)strlen("R"));
		server->SetTimeoutTime(30000,RakNet::UNASSIGNED_SYSTEM_ADDRESS);
		//	RakNet::PacketLogger packetLogger;
		//	server->AttachPlugin(&packetLogger);

		// Record the first client that connects to us so we can pass it to the ping function
		clientID =RakNet::UNASSIGNED_SYSTEM_ADDRESS;

		// A server
		puts("Enter the server port to listen on");
		Gets(portstring,sizeof(portstring));
		if (portstring[0]==0)
			strcpy_s(portstring, "20");

		puts("Starting server.");
		// Starting the server is very simple.  2 players allowed.
		// 0 means we don't care about a connectionValidationInteger, and false
		// for low priority threads
		// I am creating two socketDesciptors, to create two sockets. One using IPV6 and the other IPV4
		socketDescriptors[0].port=atoi(portstring);
		socketDescriptors[0].socketFamily=AF_INET; // Test out IPV4
		socketDescriptors[1].port=atoi(portstring);
		socketDescriptors[1].socketFamily=AF_INET6; // Test out IPV6
		bool b = server->Startup(4, socketDescriptors, 2 )==RakNet::RAKNET_STARTED;
		server->SetMaximumIncomingConnections(4);
		if (!b)
		{
			printf("Failed to start dual IPV4 and IPV6 ports. Trying IPV4 only.\n");

			// Try again, but leave out IPV6
			b = server->Startup(4, socketDescriptors, 1 )==RakNet::RAKNET_STARTED;
			if (!b)
			{
				puts("Server failed to start.  Terminating.");
				exit(1);
			}
		}
		server->SetOccasionalPing(true);
		server->SetUnreliableTimeout(1000);

		DataStructures::List<RakNet::RakNetSmartPtr < RakNet::RakNetSocket> > sockets;
		server->GetSockets(sockets);
		printf("Socket addresses used by RakNet:\n");
		for (unsigned int i=0; i < sockets.Size(); i++)
		{
			printf("%i. %s\n", i+1, sockets[i]->boundAddress.ToString(true));
		}

		printf("\nMy IP addresses:\n");
		for (unsigned int i=0; i < server->GetNumberOfAddresses(); i++)
		{
			printf("%i. %s\n", i+1, server->GetLocalIP(i));
		}

		printf("\nMy GUID is %s\n", server->GetGuidFromSystemAddress(RakNet::UNASSIGNED_SYSTEM_ADDRESS).ToString());
		puts("'quit' to quit. 'stat' to show stats. 'ping' to ping.\n'pingip' to ping an ip address\n'ban' to ban an IP from connecting.\n'kick to kick the first connected player.\nType to talk.");
		for (int i = 0; i < MAX_CLIENTS; i++)
		{
			clients[i] = 0;
			setVoidGameState(i);
		}
		return 1;
	}

	void update()
	{
		// Get a packet from either the server or the client
		
		for (p=server->Receive(); p; server->DeallocatePacket(p), p=server->Receive())
		{
			lastClient = p->systemAddress;
			//lastClient.address.addr4.sin_port = p->systemAddress.debugPort;
		
			int game;
			// We got a packet, get the identifier with our handy function
			packetIdentifier = GetPacketIdentifier(p);

			// Check if this is a network message packet
			switch (packetIdentifier)
			{
			case ID_DISCONNECTION_NOTIFICATION:
				// Connection lost normally
				printf("ID_DISCONNECTION_NOTIFICATION from %s\n", p->systemAddress.ToString(true));
				int number;
				number = getClientID(p->systemAddress);
				deleteClientInfo(number);
				setVoidGameState(number);
				gameState[number].client = -1;
				break;


			case ID_NEW_INCOMING_CONNECTION:
				// Somebody connected.  We have their IP now
				printf("ID_NEW_INCOMING_CONNECTION from %s with GUID %s\n", p->systemAddress.ToString(true), p->guid.ToString());
				int num;
				clientID=p->systemAddress; // Record the player ID of the client
				setUnusedClient(num, p->systemAddress);
				*clients[num] = p->systemAddress;
				//clients[num]->address.addr4.sin_port = p->systemAddress.debugPort;
				gameState[num].client = num;
				break;

			case ID_INCOMPATIBLE_PROTOCOL_VERSION:
				printf("ID_INCOMPATIBLE_PROTOCOL_VERSION\n");
				break;

			case ID_CONNECTED_PING:
			case ID_UNCONNECTED_PING:
				printf("Ping from %s\n", p->systemAddress.ToString(true));
				break;

			case ID_CONNECTION_LOST:
				// Couldn't deliver a reliable packet - i.e. the other system was abnormally
				// terminated
				printf("ID_CONNECTION_LOST from %s\n", p->systemAddress.ToString(true));;
				break;
			case ID_NEW_GAME:
				game = getClientID(p->systemAddress);
				setDefaultGameState(game);
				gameState[game].client = game;
				sendGameState(game);
				setVoidGameState(game);
				printf("Started a new game for %d\n",p->systemAddress.address.addr4.sin_port);
				//server->Send("OMG MESSAGE", (int) strlen("OMG MESSAGE")+1, HIGH_PRIORITY, RELIABLE_ORDERED, 0, lastClient, true);
				break;
			case ID_GAME_STATE:
				game = getClientID(p->systemAddress);
				gameState[game].convert((char*)p->data);
				break;
			default:
				// The server knows the static data of all clients, so we can prefix the message
				// With the name data
				printf("%s\n", p->data);

				// Relay the message.  We prefix the name for other clients.  This demonstrates
				// That messages can be changed on the server before being broadcast
				// Sending is the same as before
				//sprintf_s(message, "%s", p->data);
				//server->Send(message, (const int) strlen(message)+1, HIGH_PRIORITY, RELIABLE_ORDERED, 0, p->systemAddress, true);
				break;
			}
			for (int i = 0; i < MAX_CLIENTS; i++)
			{
				if(!gameState[i].isEmpty())
				{
					gameState[i].copy(gameMath.clientInfo(gameState[i]));
					sendGameState(i);
					setVoidGameState(i);
				}
			}
		}
	}

};